/*
    Copyright 2008, 2009, 2010 Brightworks, Inc.

    This file is part of Language Mentor.

    Language Mentor is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Language Mentor is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Language Mentor.  If not, see <http://www.gnu.org/licenses/>.
*/
/*
    Responsibilities:
        - Playing audio - specifically the current selected module version and its current selected chunk
        - When a chunk and/or a module version ends, changing the current selected module version and/or chunk
        - If asked to play a moduleVersion whose files are not downloaded, asks for download and/or informs user

    attemptToPlayOrResumeSelectedModuleVersion() gets things started. We use the word "attempt" to indicate that
        we're not assuming that the module version's files are already downloaded. There is no equivalent
        attemptToPlaySelectedChunk() because we don't start a module version until all chunk assets are
        downloaded. This is to keep the code as simple as possible. In the future we may want to allow a module
        to start once some of its chunk assets have been downloaded - we'll see how much the current approach
        impacts performance before deciding on this.
*/
package com.langcollab.languagementor.controller.audio
{
    import com.brightworks.util.Debug;
    import com.langcollab.languagementor.constant.Constant_LearningModeLabels;
    import com.langcollab.languagementor.constant.Constant_Misc;
    import com.langcollab.languagementor.event.Event_ApplicationState;
    import com.langcollab.languagementor.model.DataManager;
    import com.langcollab.languagementor.model.MainModel;
    import com.langcollab.languagementor.vo.ChunkFileVO;
    import com.langcollab.languagementor.vo.ChunkVO;
    import com.langcollab.languagementor.vo.ModuleVersionVO;

    import flash.events.IEventDispatcher;
    import flash.filesystem.File;

    import mx.binding.utils.ChangeWatcher;
    import mx.collections.ArrayCollection;
    import mx.events.PropertyChangeEvent;

    public class AudioController
    {
        private static var _audioController:com.langcollab.languagementor.controller.audio.AudioController;

        [Dispatcher]
        public var dispatcher:IEventDispatcher;

        [Inject]
        public var model:MainModel;

        private var _currentPrimaryChunkSequenceStrategy:ISequenceStrategy;
        private var _currentModuleVersionAudioSequence:AudioSequenceBranch;
        private var _currentModuleVersionVO:ModuleVersionVO;
        private var _isLoopingChunk:Boolean = false;
        private var _isPaused:Boolean = false;
        private var _isPlaying:Boolean = false;
        private var _isTempChunkSequenceStrategyActive:Boolean = false;
        private var _tempChunkSequenceStrategyRemainingChunks:int;

        public function AudioController()
        {
            init();
        }

        // ****************************************************
        //
        //          Public Methods
        //
        // ****************************************************

        public function attemptToPlayOrResumeSelectedModuleVersion():void
        {
            clearTempChunkSequenceStrategy();
            if (model.selectedModuleVersionVO == null) 
                Debug.error_Fatal( new Error("AudioController.attemptToPlayOrResumeSelectedModuleVersion(): ModelLocator.selectedModuleVersionVO is null"));
            if (model.selectedModuleVersionVO == _currentModuleVersionVO)
            {
                if (_isPaused)
                {
                    resumeCurrentModuleVersion();
                }
                else
                {
                    Debug.error_Fatal("AudioController.attemptToPlayOrResumeSelectedModuleVersion(): _currentModuleVersionVO equals selected module version in model, but !_isPaused");
                }
            }
            else
            {
                playSelectedModuleVersion();
            }
        }

        public function clearTempChunkSequenceStrategy():void
        {
            if (_isTempChunkSequenceStrategyActive)
            {
                _isTempChunkSequenceStrategyActive = false;
                _tempChunkSequenceStrategyRemainingChunks = 0;
                _currentModuleVersionAudioSequence.setSequenceStrategy(_currentPrimaryChunkSequenceStrategy, "chunk");
            }
        }

        public function iterateChunk(direction:int=1):void
        {
            if (Math.round(Math.abs(direction)) != 1) {
                Debug.warning("AudioController.iterateChunk() can be passed either +1 or -1, but was passed '" + direction + "'.");
                return;
            }
            var newModuleVersionIndex:int;
            var e:Event_ApplicationState = new Event_ApplicationState(Event_ApplicationState.CHANGE_CURRENT_CHUNK_INDEX);
            var newChunkIndex:int = model.selectedChunkIndex + direction;
            if ((newChunkIndex >= 0) && (newChunkIndex < model.getSelectedModuleVersionChunkCount())) 
            {
                // We're not changing modules
                if (_isTempChunkSequenceStrategyActive)
                {
                    _tempChunkSequenceStrategyRemainingChunks --;
                    if (_tempChunkSequenceStrategyRemainingChunks == 0)
                        clearTempChunkSequenceStrategy();
                }
                e.newChunkIndex = newChunkIndex;
                dispatcher.dispatchEvent(e);
            }
            else if (newChunkIndex < 0) 
            {
                // We need to move back to the previous module
                // For now we always move out of tempChunkSequenceStrategy mode if we change modules
                clearTempChunkSequenceStrategy();
                ////
                //cleanupCurrentModuleVersion();
                if (model.selectedModuleVersionIndex == 0) {
                    newModuleVersionIndex = model.getSelectedModuleVersionChunkCount();
                }
                else {
                    newModuleVersionIndex = model.selectedModuleVersionIndex - 1;
                }
                    ////
                    // Need to also play final chunk in module, rather than first, yes?
                    // Event's 'true' arg is bPlayNow
                    // Can either convert this to 'playHow' with 1st, last, and 'don't' option, or
                    // Pass false here, and call other events, using EventGenerator, or
                    // other?
                    //new ChangeCurrentModuleVersionIndexEvent(newModuleVersionIndex, true).dispatch();
            }
            else if (newChunkIndex >= model.getSelectedModuleVersionChunkCount()) 
            {
                // We need to move forward to the next module
                // For now we always move out of tempChunkSequenceStrategy mode if we change modules
                clearTempChunkSequenceStrategy();
                cleanupCurrentModuleVersion();
                if (model.selectedModuleVersionIndex < (model.getSelectedModuleVersionsCount() - 1)) {
                    newModuleVersionIndex = model.selectedModuleVersionIndex + 1;
                }
                else {
                    newModuleVersionIndex = 0;
                }
                e.newChunkIndex = newChunkIndex;
                e.playNow = true;
                dispatcher.dispatchEvent(e);
            }
            else {
                Debug.warning("AudioController.iterateChunk(): no matching if clause");
            }
        }

        public function onCurrentChunkIndexChange(index:int):void
        {
            //trace("AudioController.onCurrentChunkIndexChange(): " + index);
            if (_currentModuleVersionAudioSequence)
            {
                //trace("AudioController.onCurrentChunkIndexChange(): " + index + " inside 'if'");
                _currentModuleVersionAudioSequence.moveToElement("module", index);
            }
        }

        public function onStartLoopMode():void
        {
            _isLoopingChunk = true;
        }

        public function onStartPlaybackMode():void
        {
            if (_currentPrimaryChunkSequenceStrategy is SequenceStrategy_NativeToTargetLearning)
            {
                _currentModuleVersionAudioSequence.setSequenceStrategy(new SequenceStrategy_NativeToTargetLearningWithPlayback(), "chunk");
            }
            else if (_currentPrimaryChunkSequenceStrategy is SequenceStrategy_NativeToTargetTranslation)
            {
                _currentModuleVersionAudioSequence.setSequenceStrategy(new SequenceStrategy_NativeToTargetTranslationWithPlayback(), "chunk");
            }
            else if (_currentPrimaryChunkSequenceStrategy is SequenceStrategy_RepeatTarget)
            {
                _currentModuleVersionAudioSequence.setSequenceStrategy(new SequenceStrategy_RepeatTargetWithPlayback(), "chunk");
            }
            else
            {
                Debug.warning("AudioController.onStartPlaybackMode(): _currentModuleVersionAudioSequence isn't playback-able");
            }
        }

        public function onStopLoopMode():void
        {
            _isLoopingChunk = false;
        }

        public function onStopPlaybackMode():void
        {
            _currentModuleVersionAudioSequence.setSequenceStrategy(_currentPrimaryChunkSequenceStrategy, "chunk");
        }

        public function onTranslate():void
        {
            var strat:SequenceStrategy_Translate = new SequenceStrategy_Translate();
            tempChangeChunkSequenceStrategyForNChunks(strat, 1);
        }

        public function pausePlayingAudioIfAny():void
        {
            if (_isPlaying)
            {
                if (_isPaused) 
                    Debug.error_Fatal("AudioController.pausePlayingAudioIfAny(): _isPaused = true");
                _isPaused = true;
                _currentModuleVersionAudioSequence.pause("chunk");
            }
        }

        public function reset():void
        {
            _isPaused = false;
            _isPlaying = false;
            cleanupCurrentModuleVersion();
        }

        public function tempChangeChunkSequenceStrategyForNChunks(chunkStrategy:ISequenceStrategy, n:int):void{
            _isTempChunkSequenceStrategyActive = true;
            _tempChunkSequenceStrategyRemainingChunks = n;
            if (!_isPaused) 
                pausePlayingAudioIfAny();
            _currentModuleVersionAudioSequence.setSequenceStrategy(chunkStrategy, "chunk")
            if (!_isPlaying)
                Debug.error_Fatal("AudioController.tempChangeChunkSequenceStrategyForNChunks(): Called when audio not playing"); // When/why would this ever happen?
            _currentModuleVersionAudioSequence.resume("chunk");
            _isPaused = false;
        }

        // ****************************************************
        //
        //          Private Methods
        //
        // ****************************************************

        private function cleanupCurrentModuleVersion():void
        {
            _currentModuleVersionVO = null;
            if (_currentModuleVersionAudioSequence)
            {
                if (_currentModuleVersionAudioSequence.isPlaying)
                {
                    _currentModuleVersionAudioSequence.stop();
                }
                _currentModuleVersionAudioSequence.dispose();
                _currentModuleVersionAudioSequence = null;
            }
        }

        private function createCurrentModuleVersionAudioSequence():void
        {
            var chunkVOs:ArrayCollection = model.dataManager.getChunkVOsSortedByLocationInOrderFromModuleVersionVO(_currentModuleVersionVO);
            _currentModuleVersionAudioSequence = new AudioSequenceBranch();
            //_currentModuleVersionAudioSequence.id = _currentModuleVersionId;
            _currentModuleVersionAudioSequence.levelId = "module"; /// use constants throughout method
            _currentModuleVersionAudioSequence.elements = new Object();
            _currentModuleVersionAudioSequence.autoAdvance = false;
            _currentModuleVersionAudioSequence.addEventListener(AudioProgressEvent.ELEMENT_COMPLETE_REPORT, onElementCompleteReport);
            var chunkIndex:int = 0;
            for each (var chunkVO:ChunkVO in chunkVOs)
            {
                var chunkElement:AudioSequenceBranch = new AudioSequenceBranch();
                chunkElement.id = chunkIndex;
                chunkElement.levelId = "chunk";
                chunkElement.elements = new Object();
                chunkElement.addEventListener(AudioProgressEvent.ELEMENT_COMPLETE_REPORT, onElementCompleteReport);
                _currentModuleVersionAudioSequence.elements[chunkIndex] = chunkElement;

                var leaf:AudioSequenceLeaf;

                leaf = new AudioSequenceLeaf_File();
                leaf.id  = Constant_Misc.LEAF_TYPE_AUDIO_NATIVE;
                var nativeChunkFileVO:ChunkFileVO = model.dataManager.getNativeChunkFileVOFromChunkVO(chunkVO);
                AudioSequenceLeaf_File(leaf).chunkFileVO = nativeChunkFileVO;
                AudioSequenceLeaf_File(leaf).url = model.audioFileDirectoryURL + File.separator + _currentModuleVersionVO.contentProviderId + File.separator + _currentModuleVersionVO.publishedModuleVersionId + File.separator + chunkVO.fileNameRoot + "." + nativeChunkFileVO.iso639_3Code + "." + Constant_Misc.FILEPATHINFO_CHUNK_FILE_EXTENSION;
                leaf.addEventListener(AudioProgressEvent.ELEMENT_COMPLETE_REPORT, onElementCompleteReport );
                leaf.addEventListener(AudioProgressEvent.IOERROR_REPORT,          onIOErrorReport         );
                chunkElement.elements[Constant_Misc.LEAF_TYPE_AUDIO_NATIVE] = leaf;

                leaf = new AudioSequenceLeaf_File();
                leaf.id  = Constant_Misc.LEAF_TYPE_AUDIO_TARGET;
                var targetChunkFileVO:ChunkFileVO = model.dataManager.getTargetChunkFileVOFromChunkVO(chunkVO);
                AudioSequenceLeaf_File(leaf).chunkFileVO = targetChunkFileVO;
                AudioSequenceLeaf_File(leaf).url = model.audioFileDirectoryURL + File.separator + _currentModuleVersionVO.contentProviderId + File.separator + _currentModuleVersionVO.publishedModuleVersionId + File.separator + chunkVO.fileNameRoot + "." + targetChunkFileVO.iso639_3Code + "." + Constant_Misc.FILEPATHINFO_CHUNK_FILE_EXTENSION;
                leaf.addEventListener(AudioProgressEvent.ELEMENT_COMPLETE_REPORT, onElementCompleteReport );
                leaf.addEventListener(AudioProgressEvent.IOERROR_REPORT,          onIOErrorReport         );
                chunkElement.elements[Constant_Misc.LEAF_TYPE_AUDIO_TARGET] = leaf;

                leaf = new AudioSequenceLeaf_Playback();
                leaf.id = Constant_Misc.LEAF_TYPE_PLAYBACK;
                /// use constants for these values
                leaf.addEventListener(AudioProgressEvent.ELEMENT_COMPLETE_REPORT, onElementCompleteReport );
                leaf.addEventListener(AudioProgressEvent.IOERROR_REPORT,          onIOErrorReport         );
                chunkElement.elements[Constant_Misc.LEAF_TYPE_PLAYBACK] = leaf;

                leaf = new AudioSequenceLeaf_Recorder();
                leaf.id = Constant_Misc.LEAF_TYPE_PAUSE_ATTEMPT;
                /// use constants for these values
                AudioSequenceLeaf_Recorder(leaf).duration = (model.dataManager.getTargetLanguageChunkFileDurationFromChunkVO(chunkVO) * 2) + 1000;
                leaf.addEventListener(AudioProgressEvent.ELEMENT_COMPLETE_REPORT, onElementCompleteReport );
                leaf.addEventListener(AudioProgressEvent.IOERROR_REPORT,          onIOErrorReport         );
                chunkElement.elements[Constant_Misc.LEAF_TYPE_PAUSE_ATTEMPT] = leaf;

                leaf = new AudioSequenceLeaf_Recorder();
                leaf.id = Constant_Misc.LEAF_TYPE_PAUSE_REPEAT;
                /// use constants for these values
                AudioSequenceLeaf_Recorder(leaf).duration = (model.dataManager.getTargetLanguageChunkFileDurationFromChunkVO(chunkVO) * 1.5) + 1000;
                leaf.addEventListener(AudioProgressEvent.ELEMENT_COMPLETE_REPORT, onElementCompleteReport );
                leaf.addEventListener(AudioProgressEvent.IOERROR_REPORT,          onIOErrorReport         );
                chunkElement.elements[Constant_Misc.LEAF_TYPE_PAUSE_REPEAT] = leaf;

                leaf = new AudioSequenceLeaf_Silence();
                leaf.id = Constant_Misc.LEAF_TYPE_PAUSE_TINY;
                AudioSequenceLeaf_Silence(leaf).duration = 500;
                leaf.addEventListener(AudioProgressEvent.ELEMENT_COMPLETE_REPORT, onElementCompleteReport );
                leaf.addEventListener(AudioProgressEvent.IOERROR_REPORT,          onIOErrorReport         );
                chunkElement.elements[Constant_Misc.LEAF_TYPE_PAUSE_TINY] = leaf;

                chunkIndex++;
            }
            updateCurrentModuleVersionAudioSequenceSequenceStrategies();
        }

        private function init():void
        {
            // The next line didn't work, so I'm making onCurrentChunkIndexChange() public and
            //    calling it directly from ChangeCurrentChunkIndexCommand
            // ChangeWatcher.watch(model, "selectedChunkIndex",     onCurrentChunkIndexChange);
            ChangeWatcher.watch(model, "currentLearningModeId", onLearningModeIdChange);
        }

        private function onElementCompleteReport(event:AudioProgressEvent):void
        {
            //trace("onElementCompleteReport(): " + event.levelId + " " + event.id);
            switch (event.levelId)
            {
                case "module":
                    // We don't do anything here - the "chunk" case handles changing modules
                    // if the ending chunk is the last chunk.
                    break;
                case "chunk":
                    if (_isLoopingChunk)
                    {
                        _currentModuleVersionAudioSequence.replayCurrentElement("module");
                    }
                    else
                    {
                        iterateChunk();
                    }
                    break;
            }
        }

        private function onIOErrorReport(event:AudioProgressEvent):void
        {
            var errorString:String = "AudioController.onIOErrorReport(): levelId:" + event.levelId + " id:" + event.id + " url:" + event.url + " message:" + event.message;
            trace("Error: " + errorString);
            Debug.error_Fatal(errorString);///
        }

        private function onLearningModeIdChange(event:PropertyChangeEvent):void
        {
            if (this._isPlaying)
                updateCurrentModuleVersionAudioSequenceSequenceStrategies(true);
        }

        private function playSelectedModuleVersion():void
        {
            cleanupCurrentModuleVersion();
            _currentModuleVersionVO = model.selectedModuleVersionVO;
            createCurrentModuleVersionAudioSequence();
            _currentModuleVersionAudioSequence.start();
            _isPlaying = true;
        }

        private function resumeCurrentModuleVersion():void
        {
            _isPaused = false;
            _currentModuleVersionAudioSequence.resume("chunk");
        }

        private function updateCurrentModuleVersionAudioSequenceSequenceStrategies(chunksOnly:Boolean=false):void
        {
            if (!chunksOnly)
            {
                var moduleStrat:ISequenceStrategy = new SequenceStrategy_Module();
                _currentModuleVersionAudioSequence.setSequenceStrategy(moduleStrat, "module")
            }
            var chunkStrategy:ISequenceStrategy;
            var learningModeToken:String = model.dataManager.getLearningModeTokenFromID(model.currentLearningModeId);
            switch (learningModeToken)
            {
                case Constant_LearningModeLabels.LISTEN_TO_TARGET:
                    chunkStrategy = new SequenceStrategy_ListenToTarget();
                    break;
                case Constant_LearningModeLabels.NATIVE_TO_TARGET_LEARNING:
                    chunkStrategy = new SequenceStrategy_NativeToTargetLearning();
                    break;
                case Constant_LearningModeLabels.NATIVE_TO_TARGET_TRANSLATION:
                    chunkStrategy = new SequenceStrategy_NativeToTargetTranslation();
                    break;
                case Constant_LearningModeLabels.REPEAT_TARGET:
                    chunkStrategy = new SequenceStrategy_RepeatTarget();
                    break;
                case Constant_LearningModeLabels.TARGET_TO_NATIVE_TRANSLATION:
                    chunkStrategy = new SequenceStrategy_TargetToNativeTranslation();
                    break;
                default:
                    Debug.error_Fatal(["AudioController.updateCurrentModuleVersionAudioSequenceSequenceStrategies(): no case for learning mode token in switch statement:", learningModeToken]);
            }
            _currentPrimaryChunkSequenceStrategy = chunkStrategy;
            _currentModuleVersionAudioSequence.setSequenceStrategy(chunkStrategy, "chunk")
        }
    }
}















